/****************************************
* James Gomez                           *
* Project 3: Target Practice P3         *
* Comp 465                              *
* Professor Barnes                      *
* 12/7/2012                             *
*****************************************/

#ifndef _CAMERA_
#define _CAMERA_

#include "GomezJamesP3_OpenGL.h"
#include "GomezJamesP3_Object3D.cpp"

#include <string>
using namespace std;


/************************************************
3D Object that sets up a static camera viewport 
*************************************************/
class Camera: public Object3D{

private:	
	GLdouble eye[3];
	GLdouble lookat[3];
	GLdouble up[3];

public:
	Camera(const string name);
	Camera(const string name, const GLdouble eye[3], const GLdouble lookat[3], 
		const GLdouble up[3]);
	Camera(const string name, const Vector3D eye, const Vector3D lookat, 
		const Vector3D up);
	~Camera(){}

	//vector-based getters and setters
	inline Vector3D getEye() const { return Vector3D(eye[0], eye[1], eye[2]); };
	inline Vector3D getLookat() const { return Vector3D(lookat[0], lookat[1], lookat[2]); };
	inline Vector3D getUp() const { return Vector3D(up[0], up[1], up[2]); };
	void setEye(const Vector3D eye);
	void setLookat(const Vector3D lookat);											
	void setUp(const Vector3D up);

	//component-based getters and setters
	void getEye(GLfloat &x, GLfloat &y, GLfloat &z) const { x=eye[0]; y=eye[1]; z=eye[2]; }
	void getLookat(GLfloat &x, GLfloat &y, GLfloat &z) const { x=lookat[0]; y=lookat[1]; z=lookat[2]; }
	void getUp(GLfloat &x, GLfloat &y, GLfloat &z) const { x=up[0]; y=up[1]; z=up[2]; }
	void setEye(const GLfloat x, const GLfloat y, const GLfloat z);
	void setLookat(const GLfloat x, const GLfloat y, const GLfloat z);
	void setUp(const GLfloat x, const GLfloat y, const GLfloat z);		

	void draw();
	virtual void setCameraView();	

protected:
	void eyeToWorldCoordinates(GLfloat m[16], GLfloat newM[16]);
	void worldToEyeCoordinates(GLfloat m[16], GLfloat newM[16]);
};


Camera::Camera(const string name): Object3D(name){
	//set scene to origin with +y as Up and -z as At
	this->eye[0] = 0; 
	this->eye[1] = 0; 
	this->eye[2] = 0;
	
	this->lookat[0] = 0; 
	this->lookat[2] = 0; 
	this->lookat[2] = -1;
	
	this->up[0] = 0;
	this->up[1] = 1; 
	this->up[2] = 0;	
}

Camera::Camera(const string name, const GLdouble eye[3], const GLdouble lookat[3],
			   const GLdouble up[3]): Object3D(name){	

	for (int i=0; i<3; i++){		
		this->eye[i] = eye[i];
		this->lookat[i] = lookat[i];
		this->up[i] = up[i];		
	}
		
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
	glTranslatef((GLfloat)eye[0], (GLfloat)eye[1], (GLfloat)eye[2]);
	glGetFloatv(GL_MODELVIEW_MATRIX, this->TM);
	glPopMatrix();
}

Camera::Camera(const string name, const Vector3D eye, const Vector3D lookat, 
		const Vector3D up): Object3D(name){

	GLdouble x, y, z;

	eye.getComponents(x, y, z);
	this->eye[0]= x; this->eye[1]= y; this->eye[2]= z;
	lookat.getComponents(x, y, z);
	this->lookat[0]= x; this->lookat[1]= y; this->lookat[2]= z;
	up.getComponents(x, y, z);
	this->up[0]= x; this->up[1]= y; this->up[2]= z;
	
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();
	glTranslatef((GLfloat)this->eye[0], (GLfloat)this->eye[1], (GLfloat)this->eye[2]);
	glGetFloatv(GL_MODELVIEW_MATRIX, this->TM);
	glPopMatrix();
}

void Camera::setEye(const Vector3D eye){
	this->eye[0]=eye.getX();
	this->eye[1]=eye.getY();
	this->eye[2]=eye.getZ(); 
}

void Camera::setLookat(const Vector3D lookat){
	this->lookat[0]=lookat.getX();
	this->lookat[1]=lookat.getY();
	this->lookat[2]=lookat.getZ();
}

void Camera::setUp(const Vector3D up){
	this->up[0]=up.getX();
	this->up[1]=up.getY();
	this->up[2]=up.getZ();
}

void Camera::setEye(const GLfloat x, const GLfloat y, const GLfloat z){
	eye[0] = x;
	eye[1] = y;
	eye[2] = z;
}

void Camera::setLookat(const GLfloat x, const GLfloat y, const GLfloat z){
	lookat[0] = x;
	lookat[1] = y;
	lookat[2] = z;
}

void Camera::setUp(const GLfloat x, const GLfloat y, const GLfloat z){
	up[0] = x;
	up[1] = y;
	up[2] = z;
}

void Camera::draw(){
	//draw camera as orange solid cube (mostly for debugging)
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();	
	glMultMatrixf(this->TM);
	
	//drawAxes(500);

	glColor3fv(orange);
	glutSolidCone(60, 140, SPHERE_SLICES, SPHERE_STACKS);
	glPopMatrix();
}

void Camera::setCameraView(){
	enum {X, Y, Z};

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	
	gluLookAt(
		eye[X], 
		eye[Y], 
		eye[Z],
		lookat[X], 
		lookat[Y], 
		lookat[Z],
		up[X], 
		up[Y], 
		up[Z]
	);
	
	GLfloat m[16], worldM[16];
	glGetFloatv(GL_MODELVIEW_MATRIX, m);		

	eyeToWorldCoordinates(m, worldM);

	setTM(worldM);
}

/* 
Converts the camera's local coordinate system to world coordinates. 
Takes the inverse of the input matrix m then negates its At vector.
The resultant matrix is stored in WorldM.
*/
void Camera::eyeToWorldCoordinates(GLfloat m[16], GLfloat worldM[16]){
	
	invertMatrixf(m, worldM);

	worldM[Rx] = -worldM[Rx];
	worldM[Ry] = -worldM[Ry];
	worldM[Rz] = -worldM[Rz];
	worldM[Ax] = -worldM[Ax];
	worldM[Ay] = -worldM[Ay];
	worldM[Az] = -worldM[Az];	
}

/*
Converts World coordinates to the camera's local coordinate system.
Negates the At vector of matrix m, then takes its inverse. The 
resultant matrix is stored in WorldM.
*/
void Camera::worldToEyeCoordinates(GLfloat m[16], GLfloat eyeM[16]){
	GLfloat temp[16];
	
	for (int i=0; i<T_MATRIX_SIZE; i++)
		temp[i] = m[i];

	temp[Rx] = -temp[Rx];
	temp[Ry] = -temp[Ry];
	temp[Rz] = -temp[Rz];
	temp[Ax] = -temp[Ax];
	temp[Ay] = -temp[Ay];
	temp[Az] = -temp[Az];

	invertMatrixf(temp, eyeM);
}

#endif